﻿namespace MagicSim.Rules

type Cont<'T> = 'T -> unit
type Delayed<'T> = Cont<'T> -> unit
type Immediate<'T> = struct
    val Value : 'T
    new (v: 'T) = { Value = v }
    end

type internal DelayedReturn<'T>() = 
    // 0 - initial, 1 - SetResult was called, 2 - Callback was called, 3 - final
    let mutable state : int = 0
    [<DefaultValue(false)>] val mutable private result : 'T
    [<DefaultValue(false)>] val mutable private cont : Cont<'T>
    
    member this.Reset() = 
        state <- 0
        this.result <- Unchecked.defaultof<'T>
        this.cont <- Unchecked.defaultof<Cont<'T>>
    
    member this.SetResult(result : 'T) = 
        match state with
        | 0 ->
            this.result <- result
            state <- 1
        | 2 -> 
            let cont = this.cont
            this.cont <- Unchecked.defaultof<Cont<'T>>
            state <- 3
            cont(result)
        | _ ->
            failwith "SetResult() cannot be called twice"

    member this.Callback : Delayed<'T> =
     fun cont ->
        match state with
        | 0 ->
            this.cont <- cont
            state <- 2
        | 1 ->
            let result = this.result
            this.result <- Unchecked.defaultof<'T>
            state <- 3
            cont(result)
        | _ ->
            failwith "Callback cannot be registered twice"
            

type internal ScriptBuilder() =
    member inline this.Return<'T>(value: 'T) = Immediate<'T>(value)
    member inline this.Zero() = Immediate<unit>(())
    
    [<OverloadID("1")>]
    member inline this.Delay<'T>(x:unit->Delayed<'T>) = x()
    [<OverloadID("2")>]
    member inline this.Delay<'T>(x:unit->Immediate<'T>) = x().Value

    [<OverloadID("1")>]
    member inline this.Bind<'T, 'Q>(result:Delayed<'T>, continuation:'T->Immediate<'Q>) : Delayed<'Q> =
        fun (outerCont : Cont<'Q>) -> 
            result (fun (t : 'T) -> 
                outerCont(continuation(t).Value))

    [<OverloadID("2")>]
    member inline this.Bind<'T, 'Q>(result:Delayed<'T>, continuation:'T->Delayed<'Q>) : Delayed<'Q> =
        fun (outerCont : Cont<'Q>) -> 
            result(fun (t : 'T) -> 
                continuation(t)(fun (u : 'Q) -> outerCont(u)))

module internal Script =
    let script = new ScriptBuilder()
